/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.ssa;

import com.android.dx.rop.code.Insn;
import com.android.dx.rop.code.LocalItem;
import com.android.dx.rop.code.RegisterSpec;
import com.android.dx.rop.code.RegisterSpecList;
import com.android.dx.rop.code.Rop;
import com.android.dx.util.ToHuman;

/**
 * An instruction in SSA form
 */
public abstract class SsaInsn implements ToHuman, Cloneable {

	/** {@code non-null;} the block that contains this instance */
	private final SsaBasicBlock block;

	/** {@code null-ok;} result register */
	private RegisterSpec result;

	/**
	 * Constructs an instance.
	 * 
	 * @param result
	 *            {@code null-ok;} initial result register. May be changed.
	 * @param block
	 *            {@code non-null;} block containing this insn. Can never
	 *            change.
	 */
	protected SsaInsn(RegisterSpec result, SsaBasicBlock block) {
		if (block == null) {
			throw new NullPointerException("block == null");
		}

		this.block = block;
		this.result = result;
	}

	/**
	 * Makes a new SSA insn form a rop insn.
	 * 
	 * @param insn
	 *            {@code non-null;} rop insn
	 * @param block
	 *            {@code non-null;} owning block
	 * @return {@code non-null;} an appropriately constructed instance
	 */
	public static SsaInsn makeFromRop(Insn insn, SsaBasicBlock block) {
		return new NormalSsaInsn(insn, block);
	}

	/** {@inheritDoc} */
	@Override
	public SsaInsn clone() {
		try {
			return (SsaInsn) super.clone();
		} catch (CloneNotSupportedException ex) {
			throw new RuntimeException("unexpected", ex);
		}
	}

	/**
	 * Like {@link com.android.dx.rop.code.Insn getResult()}.
	 * 
	 * @return result register
	 */
	public RegisterSpec getResult() {
		return result;
	}

	/**
	 * Set the result register.
	 * 
	 * @param result
	 *            {@code non-null;} the new result register
	 */
	protected void setResult(RegisterSpec result) {
		if (result == null) {
			throw new NullPointerException("result == null");
		}

		this.result = result;
	}

	/**
	 * Like {@link com.android.dx.rop.code.Insn getSources()}.
	 * 
	 * @return {@code non-null;} sources list
	 */
	abstract public RegisterSpecList getSources();

	/**
	 * Gets the block to which this insn instance belongs.
	 * 
	 * @return owning block
	 */
	public SsaBasicBlock getBlock() {
		return block;
	}

	/**
	 * Returns whether or not the specified reg is the result reg.
	 * 
	 * @param reg
	 *            register to test
	 * @return true if there is a result and it is stored in the specified
	 *         register
	 */
	public boolean isResultReg(int reg) {
		return result != null && result.getReg() == reg;
	}

	/**
	 * Changes the result register if this insn has a result. This is used
	 * during renaming.
	 * 
	 * @param reg
	 *            new result register
	 */
	public void changeResultReg(int reg) {
		if (result != null) {
			result = result.withReg(reg);
		}
	}

	/**
	 * Sets the local association for the result of this insn. This is sometimes
	 * updated during the SsaRenamer process.
	 * 
	 * @param local
	 *            {@code null-ok;} new debug/local variable info
	 */
	public final void setResultLocal(LocalItem local) {
		LocalItem oldItem = result.getLocalItem();

		if (local != oldItem
				&& (local == null || !local.equals(result.getLocalItem()))) {
			result = RegisterSpec.makeLocalOptional(result.getReg(),
					result.getType(), local);
		}
	}

	/**
	 * Map registers after register allocation.
	 * 
	 * @param mapper
	 *            {@code non-null;} mapping from old to new registers
	 */
	public final void mapRegisters(RegisterMapper mapper) {
		RegisterSpec oldResult = result;

		result = mapper.map(result);
		block.getParent().updateOneDefinition(this, oldResult);
		mapSourceRegisters(mapper);
	}

	/**
	 * Maps only source registers.
	 * 
	 * @param mapper
	 *            new mapping
	 */
	abstract public void mapSourceRegisters(RegisterMapper mapper);

	/**
	 * Returns the Rop opcode for this insn, or null if this is a phi insn.
	 * TODO: Move this up into NormalSsaInsn.
	 * 
	 * @return {@code null-ok;} Rop opcode if there is one.
	 */
	abstract public Rop getOpcode();

	/**
	 * Returns the original Rop insn for this insn, or null if this is a phi
	 * insn. TODO: Move this up into NormalSsaInsn.
	 * 
	 * @return {@code null-ok;} Rop insn if there is one.
	 */
	abstract public Insn getOriginalRopInsn();

	/**
	 * Gets the spec of a local variable assignment that occurs at this
	 * instruction, or null if no local variable assignment occurs. This may be
	 * the result register, or for {@code mark-local} insns it may be the
	 * source.
	 * 
	 * @see com.android.dx.rop.code.Insn#getLocalAssignment()
	 * @return {@code null-ok;} a local-associated register spec or null
	 */
	public RegisterSpec getLocalAssignment() {
		if (result != null && result.getLocalItem() != null) {
			return result;
		}

		return null;
	}

	/**
	 * Indicates whether the specified register is amongst the registers used as
	 * sources for this instruction.
	 * 
	 * @param reg
	 *            the register in question
	 * @return true if the reg is a source
	 */
	public boolean isRegASource(int reg) {
		return null != getSources().specForRegister(reg);
	}

	/**
	 * Transform back to ROP form. TODO: Move this up into NormalSsaInsn.
	 * 
	 * @return {@code non-null;} a ROP representation of this instruction, with
	 *         updated registers.
	 */
	public abstract Insn toRopInsn();

	/**
	 * @return true if this is a PhiInsn or a normal move insn
	 */
	public abstract boolean isPhiOrMove();

	/**
	 * Returns true if this insn is considered to have a side effect beyond that
	 * of assigning to the result reg.
	 * 
	 * @return true if this insn is considered to have a side effect beyond that
	 *         of assigning to the result reg.
	 */
	public abstract boolean hasSideEffect();

	/**
	 * @return true if this is a move (but not a move-operand or move-exception)
	 *         instruction
	 */
	public boolean isNormalMoveInsn() {
		return false;
	}

	/**
	 * @return true if this is a move-exception instruction. These instructions
	 *         must immediately follow a preceeding invoke*
	 */
	public boolean isMoveException() {
		return false;
	}

	/**
	 * @return true if this instruction can throw.
	 */
	abstract public boolean canThrow();

	/**
	 * Accepts a visitor.
	 * 
	 * @param v
	 *            {@code non-null} the visitor
	 */
	public abstract void accept(Visitor v);

	/**
	 * Visitor interface for this class.
	 */
	public static interface Visitor {

		/**
		 * Any non-phi move instruction
		 * 
		 * @param insn
		 *            {@code non-null;} the instruction to visit
		 */
		public void visitMoveInsn(NormalSsaInsn insn);

		/**
		 * Any phi insn
		 * 
		 * @param insn
		 *            {@code non-null;} the instruction to visit
		 */
		public void visitPhiInsn(PhiInsn insn);

		/**
		 * Any insn that isn't a move or a phi (which is also a move).
		 * 
		 * @param insn
		 *            {@code non-null;} the instruction to visit
		 */
		public void visitNonMoveInsn(NormalSsaInsn insn);
	}
}
